﻿using ChineseAreaService;
using Microsoft.AspNet.Hosting;
using Microsoft.AspNet.Identity;
using Microsoft.AspNet.Mvc;
using Microsoft.Data.Entity;
using Microsoft.Extensions.Caching.Distributed;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.PlatformAbstractions;
using Sino.CapacityCloud.Repositories.Models;
using Sino.CapacityCloud.Services;
using Sino.CapacityCloud.WebApi.ApiControllers;
using Sino.Tms.Primitives;
using Sino.WebApi.Framework.Responses;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace Sino.CapacityCloud.WebApi.ApiControllers
{

    public class LineController
        : ApiController<LineController>
    {

        private readonly IChinaAreaService _chinaAreaService;

        public LineController(
            IHostingEnvironment env,
            IApplicationEnvironment applicationEnvironment,
            IDistributedCache cache,
            ITokenGenertationService tokenGenertationService,
            ApplicationDbContext dbContext,
            ApplicationUserManager userManager,
            ApplicationRoleManager roleManager,
            ApplicationSignInManager signInManager,
            ILoggerFactory loggerFactory, IChinaAreaService chinaAreaService)
            : base(env, applicationEnvironment, cache, tokenGenertationService, dbContext, userManager, roleManager, signInManager, loggerFactory)
        {
            this._chinaAreaService = chinaAreaService;
        }

        public async Task<JsonResult> AddCommonLine(string startingPlaceCode, string destinationCode)
        {
            try
            {
                var user = await this.UserManager.FindByNameAsync(this.User.Identity.Name);
                if (user != null)
                {
                    var commonLineCount = this.DataContext.CommonTransportLinkages.Where(x => x.UserId == user.Id).Count();

                    if (commonLineCount == 10)
                    {
                        throw new InvalidOperationException("Exceed maximum number of common lines for one user.");
                    }

                    var transportLinkageId = this.InsertOrUpdateTransportLinkage(startingPlaceCode, destinationCode);
                    var commonTransportLinkage = new CommonTransportLinkage()
                    {
                        TransportLinkageId = transportLinkageId,
                        UserId = user.Id,
                        OperationTime = DateTime.Now,
                        Remarks = ""
                    };
                    this.DataContext.CommonTransportLinkages.Add(commonTransportLinkage);
                    this.DataContext.SaveChanges();
                    var response = new LineInfoResponse()
                    {
                        Line = new Line()
                        {
                            Id = commonTransportLinkage.CommonTransportLinkageId,
                            StartingPlaceCode = commonTransportLinkage.TransportLinkage.StartingPlaceCode,
                            StartingPlaceName = commonTransportLinkage.TransportLinkage.StartingPlaceName,
                            StartingPlaceLatitude = commonTransportLinkage.TransportLinkage.StartingPlaceLocation.Latitude,
                            StartingPlaceLongitude = commonTransportLinkage.TransportLinkage.StartingPlaceLocation.Longitude,
                            DestinationCode = commonTransportLinkage.TransportLinkage.DestinationPlaceCode,
                            DestinationName = commonTransportLinkage.TransportLinkage.DestinationPlaceName,
                            DestinationLatitude = commonTransportLinkage.TransportLinkage.DestinationPlaceLocation.Latitude,
                            DestinationLongitude = commonTransportLinkage.TransportLinkage.DestinationPlaceLocation.Longitude,
                            OperationTime = commonTransportLinkage.OperationTime
                        }
                    };
                    return await this.PackageResultAsync<LineInfoResponse>(response);
                }
                throw new SystemException("Failed to find the specified ApplicationUser object.");
            }
            catch (Exception ex)
            {
                this._logger.LogError($"Failed to add common line because: {ex.Message + Environment.NewLine + ex.StackTrace}");
                return await this.PackageResultAsync<LineInfoResponse>(ex);
            }
        }
        public async Task<JsonResult> AddUserCommonLine(string userId, string startingPlaceCode, string destinationCode)
        {
            try
            {
                var user = await this.UserManager.FindByIdAsync(userId);
                if (user != null)
                {
                    var transportLinkageId = this.InsertOrUpdateTransportLinkage(startingPlaceCode, destinationCode);
                    var commonTransportLinkage = new CommonTransportLinkage()
                    {
                        TransportLinkageId = transportLinkageId,
                        UserId = user.Id,
                        Remarks = "",
                        OperationTime = DateTime.Now
                    };
                    this.DataContext.CommonTransportLinkages.Add(commonTransportLinkage);
                    this.DataContext.SaveChanges();
                    var response = new LineInfoResponse()
                    {
                        Line = new Line()
                        {
                            Id = commonTransportLinkage.CommonTransportLinkageId,
                            StartingPlaceCode = commonTransportLinkage.TransportLinkage.StartingPlaceCode,
                            StartingPlaceName = commonTransportLinkage.TransportLinkage.StartingPlaceName,
                            StartingPlaceLatitude = commonTransportLinkage.TransportLinkage.StartingPlaceLocation.Latitude,
                            StartingPlaceLongitude = commonTransportLinkage.TransportLinkage.StartingPlaceLocation.Longitude,
                            DestinationCode = commonTransportLinkage.TransportLinkage.DestinationPlaceCode,
                            DestinationName = commonTransportLinkage.TransportLinkage.DestinationPlaceName,
                            DestinationLatitude = commonTransportLinkage.TransportLinkage.DestinationPlaceLocation.Latitude,
                            DestinationLongitude = commonTransportLinkage.TransportLinkage.DestinationPlaceLocation.Longitude,
                            OperationTime = commonTransportLinkage.OperationTime
                        }
                    };
                    return await this.PackageResultAsync<LineInfoResponse>(response);
                }
                throw new SystemException("Failed to find the specified ApplicationUser object.");
            }
            catch (Exception ex)
            {
                this._logger.LogError($"Failed to add common line because: {ex.Message + Environment.NewLine + ex.StackTrace}");
                return await this.PackageResultAsync<LineInfoResponse>(ex);
            }
        }
        public async Task<JsonResult> AddFocusLine(string startingPlaceCode, string destinationCode)
        {
            try
            {
                var user = await this.UserManager.FindByNameAsync(this.User.Identity.Name);
                if (user != null)
                {
                    var favouriteLineCount = this.DataContext.FavouriteTransportLinkages.Where(x => x.UserId == user.Id).Count();

                    if (favouriteLineCount == 10)
                    {
                        throw new InvalidOperationException("Exceed maximum number of favourite lines for one user.");
                    }

                    var transportLinkageId = this.InsertOrUpdateTransportLinkage(startingPlaceCode, destinationCode);
                    var favouriteTransportLinkage = new FavouriteTransportLinkage()
                    {
                        TransportLinkageId = transportLinkageId,
                        UserId = user.Id,
                        OperationTime = DateTime.Now,
                        Remarks = "",
                    };
                    this.DataContext.FavouriteTransportLinkages.Add(favouriteTransportLinkage);
                    this.DataContext.SaveChanges();
                    var response = new LineInfoResponse()
                    {
                        Line = new Line()
                        {
                            Id = favouriteTransportLinkage.FavouriteTransportLinkageId,
                            StartingPlaceCode = favouriteTransportLinkage.TransportLinkage.StartingPlaceCode,
                            StartingPlaceName = favouriteTransportLinkage.TransportLinkage.StartingPlaceName,
                            StartingPlaceLatitude = favouriteTransportLinkage.TransportLinkage.StartingPlaceLocation.Latitude,
                            StartingPlaceLongitude = favouriteTransportLinkage.TransportLinkage.StartingPlaceLocation.Longitude,
                            DestinationCode = favouriteTransportLinkage.TransportLinkage.DestinationPlaceCode,
                            DestinationName = favouriteTransportLinkage.TransportLinkage.DestinationPlaceName,
                            DestinationLatitude = favouriteTransportLinkage.TransportLinkage.DestinationPlaceLocation.Latitude,
                            DestinationLongitude = favouriteTransportLinkage.TransportLinkage.DestinationPlaceLocation.Longitude,
                            OperationTime = favouriteTransportLinkage.OperationTime
                        }
                    };
                    return await this.PackageResultAsync<LineInfoResponse>(response);
                }
                throw new SystemException("Failed to find the specified ApplicationUser object.");
            }
            catch (Exception ex)
            {
                this._logger.LogError($"Failed to add focus line because: {ex.Message + Environment.NewLine + ex.StackTrace}");
                return await this.PackageResultAsync<LineInfoResponse>(ex);
            }
        }
        public async Task<JsonResult> AddUserFocusLine(string userId, string startingPlaceCode, string destinationCode)
        {
            try
            {
                var user = await this.UserManager.FindByIdAsync(userId);
                if (user != null)
                {
                    var transportLinkageId = this.InsertOrUpdateTransportLinkage(startingPlaceCode, destinationCode);
                    var favouriteTransportLinkage = new FavouriteTransportLinkage()
                    {
                        TransportLinkageId = transportLinkageId,
                        UserId = user.Id,
                        OperationTime = DateTime.Now,
                        Remarks = "",
                    };
                    this.DataContext.FavouriteTransportLinkages.Add(favouriteTransportLinkage);
                    this.DataContext.SaveChanges();
                    var response = new LineInfoResponse()
                    {
                        Line = new Line()
                        {
                            Id = favouriteTransportLinkage.FavouriteTransportLinkageId,
                            StartingPlaceCode = favouriteTransportLinkage.TransportLinkage.StartingPlaceCode,
                            StartingPlaceName = favouriteTransportLinkage.TransportLinkage.StartingPlaceName,
                            StartingPlaceLatitude = favouriteTransportLinkage.TransportLinkage.StartingPlaceLocation.Latitude,
                            StartingPlaceLongitude = favouriteTransportLinkage.TransportLinkage.StartingPlaceLocation.Longitude,
                            DestinationCode = favouriteTransportLinkage.TransportLinkage.DestinationPlaceCode,
                            DestinationName = favouriteTransportLinkage.TransportLinkage.DestinationPlaceName,
                            DestinationLatitude = favouriteTransportLinkage.TransportLinkage.DestinationPlaceLocation.Latitude,
                            DestinationLongitude = favouriteTransportLinkage.TransportLinkage.DestinationPlaceLocation.Longitude,
                            OperationTime = favouriteTransportLinkage.OperationTime
                        }
                    };
                    return await this.PackageResultAsync<LineInfoResponse>(response);
                }
                throw new SystemException("Failed to find the specified ApplicationUser object.");
            }
            catch (Exception ex)
            {
                this._logger.LogError($"Failed to add focus line because: {ex.Message + Environment.NewLine + ex.StackTrace}");
                return await this.PackageResultAsync<LineInfoResponse>(ex);
            }
        }
        public async Task<JsonResult> DeleteCommonLine(string lineId)
        {
            try
            {
                var user = await this.UserManager.FindByNameAsync(this.User.Identity.Name);
                if (user != null)
                {
                    var line = this.DataContext.CommonTransportLinkages.FirstOrDefault(x => x.CommonTransportLinkageId == lineId);
                    if (line != null)
                    {
                        this.DataContext.CommonTransportLinkages.Remove(line);
                        this.DataContext.SaveChanges();
                        return await this.PackageResultAsync<VoidResponse>();
                    }
                    throw new SystemException($"Failed to find the specified CommonTransportLinkage object with id = {lineId}.");
                }
                throw new SystemException("Failed to find the specified ApplicationUser object.");
            }
            catch (Exception ex)
            {
                this._logger.LogError($"Failed to delete common line {lineId} because: {ex.Message + Environment.NewLine + ex.StackTrace}");
                return await this.PackageResultAsync<VoidResponse>(ex);
            }
        }

        public async Task<JsonResult> DeleteFocusLine(string lineId)
        {
            try
            {
                var user = await this.UserManager.FindByNameAsync(this.User.Identity.Name);
                if (user != null)
                {
                    var line = this.DataContext.FavouriteTransportLinkages.FirstOrDefault(x => x.FavouriteTransportLinkageId == lineId);
                    if (line != null)
                    {
                        this.DataContext.FavouriteTransportLinkages.Remove(line);
                        this.DataContext.SaveChanges();
                        return await this.PackageResultAsync<VoidResponse>();
                    }
                    throw new SystemException($"Failed to find the specified FavouriteTransportLinkage object with id = {lineId}.");
                }
                throw new SystemException("Failed to find the specified ApplicationUser object.");
            }
            catch (Exception ex)
            {
                this._logger.LogError($"Failed to delete focus line {lineId} because: {ex.Message + Environment.NewLine + ex.StackTrace}");
                return await this.PackageResultAsync<VoidResponse>(ex);
            }
        }
        public async Task<JsonResult> GetCommonLines()
        {
            try
            {
                var user = await this.UserManager.FindByNameAsync(this.User.Identity.Name);
                if (user != null)
                {
                    var lines = this.DataContext.CommonTransportLinkages
                        //.Include(x => x.StartingPlaceLocation)
                        //.Include(x => x.DestinationPlaceLocation)
                        .Include(x => x.TransportLinkage)
                        .Where(x => x.UserId == user.Id).ToList();
                    var response = new LineListResponse()
                    {
                        Lines = lines.Select(x => new Line()
                        {
                            Id = x.CommonTransportLinkageId,
                            StartingPlaceCode = x.TransportLinkage.StartingPlaceCode,
                            StartingPlaceName = x.TransportLinkage.StartingPlaceName,
                            StartingPlaceLatitude = x.TransportLinkage.StartingPlaceLocation.Latitude,
                            StartingPlaceLongitude = x.TransportLinkage.StartingPlaceLocation.Longitude,
                            DestinationCode = x.TransportLinkage.DestinationPlaceCode,
                            DestinationName = x.TransportLinkage.DestinationPlaceName,
                            DestinationLatitude = x.TransportLinkage.DestinationPlaceLocation.Latitude,
                            DestinationLongitude = x.TransportLinkage.DestinationPlaceLocation.Longitude,
                            OperationTime = x.OperationTime
                        }).ToArray()
                    };
                    return await this.PackageResultAsync<LineListResponse>(response);
                }
                throw new SystemException("Failed to find the specified ApplicationUser object.");
            }
            catch (Exception ex)
            {
                this._logger.LogError($"Failed to get common lines because: {ex.Message + Environment.NewLine + ex.StackTrace}");
                return await this.PackageResultAsync<LineListResponse>(ex);
            }
        }
        public async Task<JsonResult> GetUserCommonLines(string userId, int startIndex, int count)
        {
            try
            {
                var user = await this.UserManager.FindByIdAsync(userId);
                if (user != null)
                {
                    var lines = this.DataContext.CommonTransportLinkages
                        //.Include(x => x.StartingPlaceLocation)
                        //.Include(x => x.DestinationPlaceLocation)
                        .Include(x => x.TransportLinkage)
                        .Where(x => x.UserId == user.Id).ToList();
                    if (lines != null && lines.Count() > 0)
                    {
                        var list = lines.Select(x => new Line()
                        {
                            Id = x.CommonTransportLinkageId,
                            StartingPlaceCode = x.TransportLinkage.StartingPlaceCode,
                            StartingPlaceName = x.TransportLinkage.StartingPlaceName,
                            StartingPlaceLatitude = x.TransportLinkage.StartingPlaceLocation.Latitude,
                            StartingPlaceLongitude = x.TransportLinkage.StartingPlaceLocation.Longitude,
                            DestinationCode = x.TransportLinkage.DestinationPlaceCode,
                            DestinationName = x.TransportLinkage.DestinationPlaceName,
                            DestinationLatitude = x.TransportLinkage.DestinationPlaceLocation.Latitude,
                            DestinationLongitude = x.TransportLinkage.DestinationPlaceLocation.Longitude,
                            OperationTime = x.OperationTime
                        });
                        var response = new ValueResponse<PageVM<Line>>()
                        {
                            Value = new PageVM<Line>()
                            {
                                List = list.Skip(startIndex).Take(count).ToList(),
                                Total = list.Count()
                            }
                        };
                        return await this.PackageResultAsync<ValueResponse<PageVM<Line>>>(response);
                    }
                    return await this.PackageResultAsync<ValueResponse<PageVM<Line>>>(new ValueResponse<PageVM<Line>>());
                }
                throw new SystemException("Failed to find the specified ApplicationUser object.");
            }
            catch (Exception ex)
            {
                this._logger.LogError($"Failed to get common lines because: {ex.Message + Environment.NewLine + ex.StackTrace}");
                return await this.PackageResultAsync<ValueResponse<PageVM<Line>>>(ex);
            }
        }
        public async Task<JsonResult> GetUserFocusLines(string userId)
        {
            try
            {
                var user = await this.UserManager.FindByIdAsync(userId);
                if (user != null)
                {
                    var lines = this.DataContext.FavouriteTransportLinkages
                        //.Include(x => x.StartingPlaceLocation)
                        //.Include(x => x.DestinationPlaceLocation)
                        .Include(x => x.TransportLinkage)
                        .Where(x => x.UserId == user.Id).ToList();
                    var response = new LineListResponse()
                    {
                        Lines = lines.Select(x => new Line()
                        {
                            Id = x.FavouriteTransportLinkageId,
                            StartingPlaceCode = x.TransportLinkage.StartingPlaceCode,
                            StartingPlaceName = x.TransportLinkage.StartingPlaceName,
                            StartingPlaceLatitude = x.TransportLinkage.StartingPlaceLocation.Latitude,
                            StartingPlaceLongitude = x.TransportLinkage.StartingPlaceLocation.Longitude,
                            DestinationCode = x.TransportLinkage.DestinationPlaceCode,
                            DestinationName = x.TransportLinkage.DestinationPlaceName,
                            DestinationLatitude = x.TransportLinkage.DestinationPlaceLocation.Latitude,
                            DestinationLongitude = x.TransportLinkage.DestinationPlaceLocation.Longitude,
                            OperationTime = x.OperationTime
                        }).ToArray()
                    };
                    return await this.PackageResultAsync<LineListResponse>(response);
                }
                throw new SystemException("Failed to find the specified ApplicationUser object.");
            }
            catch (Exception ex)
            {
                this._logger.LogError($"Failed to get focused lines because: {ex.Message + Environment.NewLine + ex.StackTrace}");
                return await this.PackageResultAsync<LineListResponse>(ex);
            }
        }
        public async Task<JsonResult> GetFocusLines()
        {
            try
            {
                var user = await this.UserManager.FindByNameAsync(this.User.Identity.Name);
                if (user != null)
                {
                    var lines = this.DataContext.FavouriteTransportLinkages
                        //.Include(x => x.StartingPlaceLocation)
                        //.Include(x => x.DestinationPlaceLocation)
                        .Include(x => x.TransportLinkage)
                        .Where(x => x.UserId == user.Id).ToList();
                    var response = new LineListResponse()
                    {
                        Lines = lines.Select(x => new Line()
                        {
                            Id = x.FavouriteTransportLinkageId,
                            StartingPlaceCode = x.TransportLinkage.StartingPlaceCode,
                            StartingPlaceName = x.TransportLinkage.StartingPlaceName,
                            StartingPlaceLatitude = x.TransportLinkage.StartingPlaceLocation.Latitude,
                            StartingPlaceLongitude = x.TransportLinkage.StartingPlaceLocation.Longitude,
                            DestinationCode = x.TransportLinkage.DestinationPlaceCode,
                            DestinationName = x.TransportLinkage.DestinationPlaceName,
                            DestinationLatitude = x.TransportLinkage.DestinationPlaceLocation.Latitude,
                            DestinationLongitude = x.TransportLinkage.DestinationPlaceLocation.Longitude,
                            OperationTime = x.OperationTime
                        }).ToArray()
                    };
                    return await this.PackageResultAsync<LineListResponse>(response);
                }
                throw new SystemException("Failed to find the specified ApplicationUser object.");
            }
            catch (Exception ex)
            {
                this._logger.LogError($"Failed to get focused lines because: {ex.Message + Environment.NewLine + ex.StackTrace}");
                return await this.PackageResultAsync<LineListResponse>(ex);
            }
        }

        private string InsertOrUpdateTransportLinkage(string startingPlaceCode, string destinationCode)
        {
            // TODO: Add param parameter checks.
            if (startingPlaceCode == destinationCode)
            {
                throw new ArgumentException($"{nameof(startingPlaceCode)} : {startingPlaceCode} == {nameof(destinationCode)} : {destinationCode}.");
            }
            var startingPlace = string.IsNullOrEmpty(startingPlaceCode) ? "全国" : this._chinaAreaService.GetAreaName(startingPlaceCode);
            var destinationPlace = string.IsNullOrEmpty(destinationCode) ? "全国" : this._chinaAreaService.GetAreaName(destinationCode);
            var transportLinkage = this.DataContext.TransportLinkages.FirstOrDefault(x => x.StartingPlaceCode == startingPlaceCode && x.DestinationPlaceCode == destinationCode);
            if (transportLinkage == null)
            {
                transportLinkage = new TransportLinkage()
                {
                    StartingPlaceCode = startingPlaceCode,
                    StartingPlaceName = startingPlace,
                    DestinationPlaceCode = destinationCode,
                    DestinationPlaceName = destinationPlace,
                    Remarks = "",
                };
                this.DataContext.TransportLinkages.Add(transportLinkage);
                this.DataContext.SaveChanges();
            }
            return transportLinkage.TransportLinkageId;
        }

    }

}